Alrighty, to get started with the story section of our game, we need to create the loading screen
and the scripts that will load all of the modules on the client and on the server.
So before we get started real quick, let's go ahead and move our Squidward AI back into server storage
because we don't need him for now.
We'll be scripting him later, but thankfully we did most of the hard work already when we scripted
all of the loading functionality on our lobby, so we can literally just copy the scripts from our lobby
and use those loaders for our loading screen and all the other stuff here in the story section of our
game.
So here inside of our lobby section, I'm literally just going to go into replicated first into starter
player scripts and as well in server script service.
And I'm going to just copy all of these scripts here.
Control C that.
And now back in our main game, I'm just going to paste those scripts in the lobby for now, because
we're just going to go ahead and copy all of the code that we have in here and use it for the same loaders
on the server as well.
So we can paste that for the server in there for our loading guy, we can just copy all of this, go
into replicated first and use that for our loading guy.
And then lastly, we can do the same thing for loading all of these scripts on the client.
So we'll just copy all of that, go to startup player scripts and use that for our start script on the
client.
Now there is a few changes that we'll want to make in our loading GUI.
So when we go back into our loading GUI handler is that I want to make a new constant for waiting for
other players, because we're not going to start the story of our horror game until all of the players
in our game have loaded in.
So I'm going to create a new wait text.
I'm going to call it wait, uh, text players or we can call it wait for players.
Let's do that instead.
Wait for players and we'll set the text equal to waiting for players.
And then what we could do is we could scroll down and once our client has finished loading, what we
could do here is we could create another while loop.
So I'm going to use task dot spawn to spawn this while loop in a new thread.
And what we're going to do is we're going to basically do the exact same thing that we've done up here,
but instead we're going to set the text equal to wait for players.
And then outside of this loop, what we're going to do is we're going to make a reference to that event
we have in replicated storage.
So inside of replicated storage in events underneath remotes, we have a player loaded remote event.
And the plan is, is that when our client finishes loading, we're going to fire to the server to let
the server know that we finished loading.
And then once all of the players have loaded, then the server is going to fire this event back to all
of the clients to let them know that, you know, everybody has loaded into the game.
So we'll just make a quick reference to this event real quick.
I'll just call it player loaded event equal to replicated storage.
Events.
Dot remotes, dot player loaded.
And I believe we're going to have to make a reference to replicated storage up here.
So let's do that real quick.
And do note that I do not have to wait for any of these children here, because we wait for the entire
game to load above up here.
So this stuff is guaranteed to be inside of replicated storage.
We don't have to wait for it.
So once we have finished loading, then what we're going to do is we're going to reference our player
loaded event and fire to the server that we have finished loading.
And then we're going to access this event again and wait for the on client event script signal to get
fired.
And we're just going to wait for this.
So this will let us know when all of the players in our game has loaded.
So fires when all players are loaded in the game.
And then finally, once all of the players have loaded into the game, then we can fade out our loading
screen.
Now to actually see all this working, we're going to have to fill out another service inside of the
server.
And that service is going to be when a player joins our game.
So we're going to open up our player join service here.
And we're going to go ahead and fill this out.
So we're going to go ahead and call this service player joined service.
And we'll make sure to return it at the end of this module script.
And we're only going to need three services in here.
One is going to be the player service, of course.
We're going to need server storage.
And then we're going to need replicated storage.
Now, inside of our variables section, we need to make references to several events.
One event is called uh Start Game which is in server storage.
So we can call this start game.
Event is equal to server storage dot events dot bind dot start game.
So when all of our players have loaded into the game and they've notified the server that they have
loaded, then we're going to fire this event to let the server know or to let the script that's going
to handle running the game, you know, start the game, and then we're going to have a reference to
the player loaded event.
So that's in replicated storage dot events dot remotes dot player loaded.
We're also going to have a reference to an event called, uh, update Guy.
And we're going to use this event to tell the players to update specific elements on their guy.
So we could call it update Guy event equal to replicated storage, dot events, dot remotes, dot update
guy.
And since we're going to be using remote events to give specific actions to players about updating their
GUI, we should create our own custom enum with different commands or different actions that we must
give to the players.
So actually inside of replicated storage, there's a section of module scripts in here with uh custom
enumerated types.
And we have a GUI actions enum in here.
So we should go ahead and fill this out to, you know, show all of the different actions that the server
may want the client to do on their end with their GUIs.
So we can go ahead and create a table called enum.
And inside of this table we can go ahead and fill out many different actions.
So for example, maybe one action the server wants the player to do is to update an objective on their
screen.
So we could call this update objectives.
Equal to update objectives.
We could have a command to tell a player that they've fulfilled a particular objective, so we could
call it objective fulfilled.
We could tell a player that they may be failed an objective.
So maybe there is an objective with a time limit.
If they fail that objective, then we want to tell them that they failed it.
So objective failed is equal to objective failed.
We may want to have an action to tell the player to update a particular task on their, uh, guy.
So if you remember, when we were turning through that guy, there was a section for tasks.
So we're going to call this update tasks is equal to update Tasks.
This is for them to show all the tasks they need to fulfill on their screens.
And then we're also going to have another one just called Update Task.
And this one's going to be for updating a particular task.
So for example, maybe one of the tasks is that you have to clean out the toilets.
And there's three toilets to clean.
Well when a player cleans one of them then we want to update that task and show a different, uh, number
count for how many tasks are left to fulfill.
And then once a task actually gets completely fulfilled, we can give them an action to do that.
So we could call this task fulfilled.
Another action we may want to do is to tell a player to add an objective on their screen, so we could
call it add objective equal to add objective.
Uh, another action we may want to do is to display that little message at the bottom of the screen
to give the player a little bit of information.
So we could call this small message.
Another action we could do is to display that countdown on their screen.
So when we have a section that's timed and we need to tick down the counter on the player's end, we
can fire an event to the player every second and we can call it something like update countdown.
We may want to have an action for perhaps controlling different frames.
So for example, when the task section is over in our story, either all the players completed all the
tasks or all the players failed to complete the tasks within the time limit, then we need to go ahead
and tell them to hide that frame of theirs so we can call it hide task frame.
We can have an event to let a player know when they've collected money.
So for example, if they find on the map a particular, you know, coin or dollar laying around and
they click it, then we can tell them to display that little money symbol on their screen.
So we could call this collected money.
Another action we may want to give to the players is in the beginning.
So we want to give them an introduction message so we can give them a command for that.
We can call it intro message.
And then another action we'll need is the frame to display when our story is over.
It'll be the outro frame so we can just call it outro frame.
And then the last action we'll need is to let a player know when they have died, basically, or to
display a death message on their screen so we can call it died.
And that's basically, I think, all of the enums we'll need for GUI actions.
And then we want to set a meta table on this enum table.
And inside of this meta table, we'll have an index meta method equal to a function where we get passed
the table itself and a key.
And basically what happens is if we index this enum with a key that does not exist, this index meta
method will get called.
And all we're going to do in here is just create an error and say something like percentage s is not
a valid member of a percentage s.
And then what we're going to do is we're going to format this with the key.
So we can use two string key.
And then for the second directive it's just going to be the name of this script.
So scriptname.
So if we index something in here that doesn't exist, it'll say, you know, whatever key is not a valid
member of GUI actions enum.
And then we'll make sure to return this enum at the very end.
So to show an example of this, what I'm going to do is I'm going to require this module script.
So I'm going to do game dot replicated storage, dot modules dot enums dot gui actions enum.
And what I'm going to do is I'm going to index this table with a key that does not exist.
So something like that.
If I hit enter boom we're going to get.
Oops, we didn't get the right message.
So actually let me like, store this in a variable.
So if we do that, then we're going to get our error.
A f dash DF is not a valid member of GitHub actions.
So it lets us know that hey, you try to access a member that does not exist.
Anyway, back to our player join service.
We can now go ahead and require our GitHub actions enum so we can call this guy actions enum.
Equal to require replicated storage.
Dot modules dot enums dot gui action enum.
The next thing I'm going to do is I'm going to create a variable to store the teleport data that gets
sent to the server.
So we're going to go ahead and call this teleport data.
And we're not going to initialize it with anything just yet.
Some other variables I'm going to create is a number to keep track of how many players are in our game,
which is currently zero.
We're going to have a number to represent the expected number of players we should have in the game.
So when we get players teleported here, they're going to get data sent with them to tell the server
how many players to expect.
So expected players currently is equal to zero.
Uh, we're also going to have a variable.
I'm going to call it time elapsed.
And we're going to use this to check, uh, how long it's been since the players have been loading.
So if a player is loading for way too long or if somebody times out or whatever, we don't want to get
stuck.
Basically, if, for example, a server of three people gets teleported to the story, but somehow only
two people make it, but it says we expect three players, we don't want to be stuck in a loop forever,
where we're waiting for a player that's never going to show up.
So we want to make sure we have a timeout in here.
That way we're not stuck in a loop forever, and that way our game isn't loading forever.
And then I'm going to create a table to store all of the players that have been loaded inside of our
game.
And then we're going to create a constant for our game.
And this is going to represent how much money we need or how much rent we owe for our game.
So we could call it rent owed is equal to like $500.
Now you could create a module script and replicated storage to store all of the different settings for
the game.
So for example, if you wanted to independently control how much rent you owe just from one script alone,
then you could create a module script to store all of the universal game settings.
But for now, we're just going to leave it inside of this constant.
Now, some private functions that we're going to need.
One is a function to check whether or not an argument matches a particular type.
So arg matches type.
You remember this function.
We can literally just go back to our original place and just copy it.
So back in our original game, if we go into something like server service, as you can see the functions
right here, we can literally just copy this.
And then back in our main game, we can just paste that right in there.
So that way we don't have to type it out.
Another function we want to have is to start our game.
So we'll just call it start game.
And all this is going to do is use our start game event and fire to whatever other script needs to know
that it's time to start the game.
Um, a function we're going to need is to listen for when a descendant is added.
So we're going to call this on descendant added.
You're going to be like, well, what's this for?
Well, when a player joins a game, we need to set up the collision groups on that player.
And we're going to need one for when a descendant is removed as well.
So that means we're going to have to set up some collision groups as well real quick.
Another function we're going to need is to create the leader stats for a player.
So we can call this function create leader stats.
For a particular player.
And we're also going to need a function to listen for when a character is added to a player.
So on character added.
We're going to want to listen to when a player is added to our game.
So on.
Player added.
And then we're going to want to listen for when a player is being removed from our game.
So we can call this function on player removing.
Okay, so down in our public functions section, what we need to do is we need to have an initialize
function for this service.
So player joined service in it.
And we're also going to need a function to start the service.
Start.
And inside of our initialize function.
What we need to do is that we need to first loop through every single player that's already inside of
our game.
So for every single player and players get players.
What we need to do is we want to use our on player added function and connect it to this particular
player.
So we want to pass a player object to this function.
And then after that we can connect to the player added event.
So players dot player added we want to connect the on player added function to this event as well.
And then we also want to connect to the player's removing event.
So player removing we want to connect our on player removing function.
And then here we also want to listen to our player loaded event.
So on server event we'll just connect a Lambda function to this.
And we'll get passed a player here.
And this is where we'll fill out that loaded players table.
And I think that's all we'll need to do here for now.
So let's go ahead and first fill out our on player added function.
So when a player gets added to our game, what we want to do first of all is we want to increment our
players in game variable.
So plus equal to one.
Then we want to connect our uh character added event.
So player oops.
Let me denote that we get a player pass to this function.
So player character added we want to connect our on character added function.
And then we also want to create the leader board or leader stats for this particular player.
Now remember we created that variable of teleport data.
Um, if teleport data isn't nil.
Then we're just going to return because every single player is going to have teleport data given to
them, and we only want to get the teleport data once.
So what we're going to do is we're going to update teleport data equal to this player.
And this player has a function inside of it called Get join data, which has all of the data that we
pass from the lobby to this server.
And then from that point we can get the table in there, which is our teleport data table.
Now for some reason this player does not have any teleport data with them.
So if not teleport data then all we're going to do is just set expected players to one.
Because for example, if we're testing in studio when a player joins the game, they're not going to
have any teleport data on them.
So we're just going to set the expected players variable to a default of one.
Otherwise, if there is teleport data, then what we need to do is we need to set expected players equal
to that teleport data dot member.
Expected player.
So we created a key in there that stores the number of players that the server should expect to join
the game.
All right.
So now let's go ahead and fill out our on character added function.
So first off what we're going to do in here is we're going to get the character.
And the first thing we actually want to set up for a character is their footsteps.
So my plan is, is that we're going to have footsteps for players.
So that way when they're walking on different surfaces, it'll play a footstep sound.
So we need to go ahead and set up that audio instance here on the server.
So we'll have a section for footsteps.
And all we'll need to do is make a reference to the root part of this player.
So character.
That humanoid brute part.
And what we're going to do is we're going to create a variable called footstep.
Sound is equal to a new sound instance, and we're going to set the parent of this sound instance to
our root part.
But first we need to set the name of this equal to footsteps.
We want to set the volume of the sound instance to something like 0.15.
The footstep sound roll off max distance.
We want to set it to something like 30.
And then for footstep sound dot roll off mode we want to set it to enum dot roll off mode dot inverse
tapered.
And then we want to do footstep sound dot play on remove equal to true.
And then we can set the parent of this footstep sound equal to the root part of the player.
Once we've done that, then what we're going to do is we're going to loop through every single child
of our character.
So what we could do here is for every single child or descendant in our character, so character get
descendants.
What we want to do is we want to call our on descendant added function and pass this child to it.
Otherwise for any other future descendants that are added to the character, we can listen to it using
the character.
Descendant added event.
So descendant added connect our on descendant added function and then when a descendant is removed from
the character, we'll listen to the descendant removing and connect our on descendant removed function.
Because the plan is here is that when we are updating the collision groups for a player, when a tool
gets added to the player's inventory or some specific item, or maybe some kind of cosmetic, whatever
it may be, we want to update the collision group for that particular descendant and set it to a collision
group where it can't collide with other particular things, and then when that descendant gets removed
from the player, maybe it gets put in their backpack or maybe it gets dropped on the ground.
Whatever the case is, then we want to re update the collision group for that item and make it back
to the default collision group.
So that means we're going to have to create several collision groups.
So go ahead and go to your model tab.
And we're going to go ahead and open up a collision Groups.
And let me go ahead and drag this out here.
And resize it.
So it's a little bit easier to read.
And I'm going to add several new groups here.
One new group is going to be for The doors in our game, so I'll just call it door.
Another new collision group we're going to create is for all of the players.
So we're going to call this player.
And then we're going to have a collision group specifically for Squidward.
And then we're going to have a collision group for all of the objects that our squid were can pass through,
but our players can't.
So, for example, let's say our main Squidward guy over here and our story, we're going to have him
walk through this door and down into the basement, but we don't want the players to walk through here.
We have an invisible wall to block this door that Squidward should be able to pass through, but our
players can't be able to pass through, so that means we need to update the collision group on that
particular item with this newer one.
So we're going to add another new collision group, and we're going to call this uh pass for squid.
So once you have filled all of these different collision groups out, what we need to do next is to
edit all of the collisions for a particular collision group.
So for the door collision group, every single, um, collision group except for Squidward should be
able to collide with the door.
So for example, a player should be able to collide with a door, and all the other stupid stuff should
be able to collide with it, but Squidward should be able to pass through doors no problem.
And then inside of pass.
For Squidward.
This also needs to be a collision group that every other thing can collide with it, but our Squidward
shouldn't be able to collide with it.
For the player collision group, our players should be able to collide with everything except other
players, because we don't want players to be stacking on top of each other.
And then for our Squidward collision group, our Squidward should be already updated.
Just like this.
We shouldn't be able to collide with doors, and we shouldn't be able to collide with anything that
has this pass for Squid Collision group.
Now, you shouldn't have to worry about going through all of the doors and stuff in the map to update
it with these different collision groups, because if you actually take a look at our doors, for example,
here's our basement blocking wall.
If you scroll down to the collision group section here, we already have the string in here for our
pass for squid collision group.
So as long as you name your collision group the exact same as I have showed you right here, then you
should be good to go.
So, for example, if you look at this door right here and we pick something random like the door itself,
we should be able to see that the collision group is set to door.
So we should be good to go there.
So back in our prior joint service, what we need to do next, I would assume, is to fill out our en
descendant added function.
So inside of en descendant added we'll get a descendant here.
So descendant.
And what we want to do when a descendant is added on to a character is that we want to check if this
descendant is a base part.
So if a descendant is a base part.
Then what we want to do is we want to set the collision group of this descendant equal to the players
collision group, because it is now part of the player.
And then for the same thing with this descendant remove function.
If the descendant passed to this function is a base part, then we want to set the collision group to
the default collision group, which is just default.
Okay.
So the next function that we could go ahead and fill out is our on player removing function.
So when a player is removed we'll get a player passed to this function.
And what we want to do is we want to set our players in game variable minus equal one.
And then we also want to set our expected players variable minus equal one.
And then what we're going to do is we're going to check whether or not this player was a loaded player.
So we're going to get an index from our table dot find function inside of our loaded players table.
And we're going to look for this particular player.
If this player is inside of this table then we're going to remove them.
So if I then table dot remove from loaded players.
At this particular index, because now what we could do is we could fill out our player loaded event
function here.
So when a player tells us that they've been loaded into the game, then what we're going to do is insert
them into the loaded players table.
So loaded player's player.
Now, since an exploiter could rapid fire this event here and fill up the table with a whole bunch of
players, we want to make sure that they're not already inside of our loaded players table.
So if not table dot find inside of loaded players this player, then we can go ahead and insert them
into the loaded players table.
Otherwise if they're already inside of there then we don't need to do anything else.
The next function we can go ahead and fill out is our create Leaderstats function.
So what we're going to do in here is we're going to create a new folder and we're going to call it leader
stats.
So instance dot new folder.
And to create a leaderboard the name of this folder has to be leader stats exactly like this.
And then we want to set the parent of this leader stats equal to the player object.
And we can go ahead and denote this as a player as well up here.
And then what we want to do is I want to create two int values inside of our leaderboard.
One is going to be for the number of deaths of this player, and the other one is going to be for how
much money they have.
So we're going to call one of these variables money which is equal to instance dot new int value.
And for this value, we're just going to set the name equal to money.
We're going to set the value.
Equal to zero.
And we're going to set the parent equal to our Leaderstats folder.
And then we're going to do the same thing with deaths.
So instance dot new int value.
We're going to set deaths dot name equal to deaths.
Deaths, dot value is equal to zero and then deaths dot parent is equal to our leader stats folder.
And then what we're going to do next is we're going to listen to when our money value changes.
So on our money int value we're going to use our get property change signal function and listen for
when the value changes in our money int value.
And when it does we want to connect a lambda function to this.
And what we're going to do is we're going to check if the money dot value.
Uh, is greater than or equal to the rent owed constant.
If it is, then what we could do is we could fire to this particular player that, hey, they fulfilled
one of the objectives, which was to, you know, uh, get enough rent so we can fire to this particular
client here or this player and give them a UI actions enum of like objective fulfilled.
And then we can pass them a table with the name of the particular objective that they fulfilled.
So we can create a key in here.
We'll just call this fulfilled objective name and we'll just set it equal to money.
And we're going to go ahead and script the guy later on.
But this is going to be what we're going to be putting here for now.
Okay, now, the last thing we need to fill out inside of our player join service is our start function.
So what we're going to do in here is I'm going to spawn a new thread.
So we're going to use task Dot.
And I guess we could use defer because we don't need it to run instantaneously.
And again, we have to spawn this in a new thread because I'm going to be putting while loops in here,
and we do not want to impede our loader on the server because it's going to jump into this function.
And if there's anything in here that's going to yield, we want to make sure that it's yielding in a
separate thread.
So what I'm going to do in here is I'm going to first check if the number of players in our game.
So get players.
If this is equal to zero somehow, then we're going to wait for players to be actually added into our
game.
So we're going to go ahead and wait for the first player to join.
Once the first player is actually in the game, then we're going to do is we're going to create a while
loop.
So while players in game is less than the expected players, what we're going to do is we're just going
to wait for one second.
We're going to set time elapsed plus equal one, and then we're going to check if time elapsed becomes
greater than something like 60s.
And if it does, then we're just going to break out of this loop.
So basically, before we even start checking whether or not the number of loaded players in our game
is equal to the number of players in our game, we actually want to verify, first of all, that all
of our players have actually joined the game.
So that's what we're going to use this while loop for.
Once this while loop ends, then we can just set time elapsed back to zero.
And then we're going to wait for one second because then afterwards what we're going to do is we're
going to spawn another while loop.
So while uh, the number of loaded players is less than the players in our game, we're going to do
is we're going to set time elapsed equal to plus equal one.
And we're also going to check if time elapsed becomes greater than or equal to 100 and 80s.
So if 100 and 80s goes by and still not all of the players have loaded into the game yet, well then
something wrong is going on.
So what we're going to do is we're going to loop through every single player and players get players.
We're going to see if this player is not within our loaded players table.
So if table dot find loaded players player, then we're just going to continue looping.
Otherwise, if this particular player is not within the loaded players table then we're just going to
kick them.
So player kick and we're just going to give a message like you took too long to load into the game because
again, the game isn't going to start until all the players have loaded.
But if for some reason some players taking way too stinking long, then we're just going to kick them
because we don't want to ruin the experience for other players.
And since we're running a while loop here, we also need to make sure to yield for one second inside
of it.
And then once all of this is done, once we have verified that the number of players on our game reach,
uh, reaches the expected players and that all of the players have loaded, then what we could do is
we can call our start game function and let the server know that it's time to start our horror game.
And I'm pretty sure that's all we need to do in here for now, actually, so we can go ahead and test
that out here.
So if we hit play.
But it doesn't appear that our loop is quite working for the waiting for players text.
So let's go ahead and take a look at that real quick.
So inside of our loading UI.
Or it could be that.
Yeah.
Uh, we should actually delete these from the workspace.
First of all, I think that's definitely conflicting there.
Oh, well, we forgot to do here actually is we forgot to change this condition right here.
So what we need to do instead is we need to replace this with a variable like players.
Ready.
So we'll create it at the top here.
So I'll just create a variable called players.
Ready.
Set it to false for now.
So once all of the players are ready and once this event gets fired, we can set players ready equal
to true.
Okay.
Perfect.
Now our loading screen is telling us that we are waiting for players.
Now, this guy is not going to fade out until that event gets fired back to our client.
And we're going to have to do that from a different service.
So when our player joins service, notifies the other service that it's time to start the game, then
that service is going to fire to all of the different players that, hey, it's time to go.
It'll tell them what to do, it'll tell them to display the intro message and all of the good stuff
like that.
Otherwise, we've got everything that we needed to complete in this lecture done for now, and I'll
go ahead and see you in the next one.